﻿using System;
using System.Collections.Generic;
using System.Text;
using Statistics;

namespace Simulator
{
    /// <summary>
    /// Единицы измерения времени
    /// </summary>
    [Serializable]
    public enum TimeUnits
    {
        days,
        hours,
        minutes,
        seconds
    }
    /// <summary>
    /// Делегат для вызова функции уведомления о ходе ИМ
    /// </summary>
    public delegate void NotifySim();
    /// <summary>
    /// Класс имитационной модели
    /// </summary>
    [Serializable]
    public class SimModel
    {
        /// <summary>
        /// Функция, которая выполняет некие действия на каждом цикле ИМ
        /// </summary>
        public NotifySim OnNotification { get; set; }
        /// <summary>
        /// количество дополнительных параметров атомарного ресурса
        /// </summary>
        public int NParams { get; set; }
        /// <summary>
        ///максимальное количество коэффициентов мат. выражения
        /// </summary>
        public int NCoefs { get; set; }
        /// <summary>
        /// Среднее время обработки проекта. Настраивается только извне
        /// </summary>
        public double Tavr { get; set; }
        /// <summary>
        ///список блоков ресурсов модели. Загружается ядром
        /// </summary>
        public List<Resource> Resources{ get; set;}
        /// <summary>
        /// список категорий ресурсов модели, по которым собирается статистика
        /// </summary>
        public List<string> Categories { get; set; }
        /// <summary>
        /// список ресурсов модели, по которым собирается статистика
        /// </summary>
        public List<Resource> Res { get; set; }
        /// <summary>
        /// Список всех блоков
        /// </summary>
        public List<Block> BlockList { get; set; }

        /// <summary>
        /// Время моделирования
        /// </summary>
        public decimal SimulationTime { get; set; }
        /// <summary>
        /// Единицы измерения времени
        /// </summary>
        public TimeUnits SimulationTimeUnits { get; set; }

        /// <summary>
        /// Источник (блок)
        /// </summary>
        public Source Source {get; set;}
        /// <summary>
        /// Сток (блок)
        /// </summary>
        public Sink Sink { get; set; }

        private SortedList<string,int> busyCats;//список числа занятых ресурсов по категориям

       

        /// <summary>
        /// Получить суммарное значение некоторого параметра в модели
        /// </summary>
        /// <param name="idParam">Номер параметра</param>
        /// <returns>Значение этого параметра</returns>
        public double GetTotalParam(int idParam)
        {
            double cost = 0, buf;
            foreach (Resource r in Resources)
            {
                buf = 0;
                foreach (AtomicRes a in r.Atoms)
                {
                    buf += a.Params[idParam];
                }
                if (!r.Shared)//если ресурс неразделяемый
                    cost += buf * r.Multiplicity;
                else
                    cost += buf;
            }
            return cost;
        }

        PointsForReport pts;
        List<Entity> entities = new List<Entity>();//список существующих в данный момент в модели сущностей

        private double sysTime = 0;//модельное время

        /// <summary>
        /// Получить текущее модельное время в секундах
        /// </summary>
        public double SysTime
        {
            get
            {
                return sysTime;
            }
        }
        /// <summary>
        /// Должно ли идти моделирование
        /// </summary>
        private bool shouldSimGo;

        /// <summary>
        /// Получить время ИМ в секундах
        /// </summary>
        public double SimulationTimeInSeconds
        {
            get
            {
                double d = 1;//множитель
                switch (SimulationTimeUnits)
                {
                    case TimeUnits.days:
                        d = 3600*8;//секунд в рабочем дне (8 ч)
                        break;
                    case TimeUnits.hours:
                        d = 3600;
                        break;
                    case TimeUnits.minutes:
                        d = 60;
                        break;
                }
                return d * Convert.ToDouble(SimulationTime);
            }
        }


        double nextEventDelay;//время до следующего события
        bool getStat = false;
        /// <summary>
        /// Запустить имитационную модель и получить полную статистику
        /// </summary>
        /// <returns>Статистическая информация о результатах эксперимента</returns>
        public PointsForReport Run()
        {
            getStat = true;
            return Run(SimulationTimeInSeconds);
        }

        /// <summary>
        /// Запустить имитационную модель и получить полную статистику
        /// </summary>
        /// <param name="SimulationTime">Время в секундах</param>
        /// <returns>Статистическая информация о результатах эксперимента</returns>
        public PointsForReport Run(double SimulationTime)
        {
            getStat = true;
            return RunStat(SimulationTime);
        }
        private double Taverage;

        private PointsForReport RunStat(double SimulationTime)
        {
            shouldSimGo = true;

            //приведем к единицам измерения времени итоговое среднее время
            string strTU = "сек";
            double d = 1;//множитель
            switch (SimulationTimeUnits)
            {
                case TimeUnits.days:
                    d = 3600 * 8;//секунд в рабочем дне (8 ч)
                    strTU = "дн";
                    break;
                case TimeUnits.hours:
                    d = 3600;
                    strTU = "час";
                    break;
                case TimeUnits.minutes:
                    d = 60;
                    strTU = "мин";
                    break;
            }

            if (getStat)
                InitializeStatistics();//инициализируем статистические данные
            int nGenerated = 0;//число сгенерированных проектов
            int nFinished = 0;//число законченных проектов
  
            entities.Clear();//очистим список сущностей
            ClearAllBlocks();//очистим все блоки от сущностей

            Entity e = Source.GenerateEntity();
            entities.Add(e);//добавим первую сущность
            e.TimeRemained = 0;

            Sink.Entities.Clear();//очистим список сущностей в стоке
            sysTime = 0;

            //запустим процесс имитационного моделирования
            while ((sysTime < SimulationTime)&&(shouldSimGo))
            {
                //найдем  сущность с минимальным временем перехода в след. блок
                e = GetNextEntity();
                
                //если настало время ее передать дальше
                if (nextEventDelay == 0)
                {
                    Block b = e.OwnerBlock;
                    switch (b.GetType().Name)
                    {
                        case "Action"://если действие, то передадим дальше
                            (b as Action).TransferEntity(ref e);
                            break;
                        case "Sink"://если сток
                            e.TimeOfExpiration = sysTime;
                            entities.Remove(e);//удалим сущность из списка сущностей в модели
                            break;
                        case "Source"://если генератор
                            (b as Source).TransferEntity(ref e);
                            e = (b as Source).GenerateEntity();
                            e.TimeOfCreation = sysTime + e.TimeRemained;//только в этот момент сущность сгенерируется
                           
                            entities.Add(e);//добавить новую сущность в список
                            nGenerated++;  
                            break;
                        case "Sync"://если синхронизатор
                            (b as Sync).TransferEntity(ref e);
                            Entity eR = (b as Sync).EntityForRemove;
                            if (eR != null)
                                entities.Remove(eR);
                            break;
                        case "Parall"://если распараллеливание
                            (b as Parall).TransferEntity(ref e);
                            entities.Add((b as Parall).CopyOfEntity);//добавим в список сущностей копию
                            break;
                        default:
                            //добавить код
                            break;
                    }
                }
                else
                {//если время передавать сущность не настало
                    //соберем статистические данные по занятости ресурсов
                    if (getStat)
                        GetBusyStatistics();
                    //сдвинем системное время и время ожидания сущностей на нужную величину
                    foreach (Entity ent in entities)
                    {
                        ent.TimeRemained -= nextEventDelay;
                    }
                    sysTime += nextEventDelay;
                    Sink.SysTime = sysTime;//настроим время в стоке в соотв. с системным
                    if (getStat)
                    {
                        //вызовем уведомление о сдвиге во времени
                        OnNotification();
                        //соберем статистические данные по занятости ресурсов
                        GetBusyStatistics();
                    }
                }
            }
            //имитационное моделирование завершилось
            nFinished = Sink.nFinished;//число завершенных проектов
            //найдем среднее время прохождения сущностей, пришедших к блоку стока
            double T = 0, dT;
            foreach (Entity ent in Sink.Entities)
            {

                dT = (ent.TimeOfExpiration - ent.TimeOfCreation);//время жизни сущности в модели
                if (dT < 0)
                    dT = dT + 0;
                T += dT;
            }

            if (nFinished != 0)//если есть сущности, пришедшие к стоку
            {
                T /= nFinished;
                T /= d;
            }
            else
                T = double.NaN;
            if (getStat)
            {
                pts.ClearSinglePointList();//очистим список точек
                pts.CreateSinglePoint("Среднее время прохождения проекта, " + strTU + ":", T);
                pts.CreateSinglePoint("Число проектов, поступивших в модель:", nGenerated);
                pts.CreateSinglePoint("Число проектов, вышедших из модели:", nFinished);
            }
            else
                Taverage = T;
            return pts;
        }

        //очистить все блоки от сущностей
        private void ClearAllBlocks()
        {
            foreach (Block b in BlockList)
            {
                b.ClearEntities();
            }
        }

        /// <summary>
        /// Запустить имитационную модель без подробной статистики
        /// </summary>
        /// <returns>Среднее время выполнения 1 проекта</returns>
        public double RunOnce()
        {
           
            //имитационное моделирование завершилось
            //найдем среднее время прохождения сущностей, пришедших к блоку стока
            getStat = false;
            RunStat(SimulationTimeInSeconds);
            return Taverage;
        }

        //Cбор статистики по занятости
        private void GetBusyStatistics()
        {
            foreach(string s in Categories)
            {
                busyCats[s] = 0;//обнулим список занятости по категориям
            }

            //изменим масштаб оси в соотв. с единицами измерения
            double scaleFactor = 1;
            switch (SimulationTimeUnits)
            {
                case TimeUnits.days:
                    scaleFactor = 3600 * 8;
                    break;
                case TimeUnits.hours:
                    scaleFactor = 3600;
                    break;
                case TimeUnits.minutes:
                    scaleFactor = 60;
                    break;
            }

            foreach (Resource r in Res)//по блокам ресурсов, занятость которых надо установить
            {
                //добавим в список стат. точек:
                //ИМЯ_РЕСУРСА  КАТ. СПИСКА   ВРЕМЯ    ЗАНЯТО_ЭКЗЕМПЛЯРОВ
                pts.Add(r.Text, "Resources", sysTime/scaleFactor, r.NBusy);
               
            }
            //по всем ресурсам
            foreach (Resource r in Resources)
            {
                //посмотрим категории ресурсов в этом блоке ресурсов
                foreach (AtomicRes a in r.Atoms)
                {
                    if(Categories.Contains(a.Category))//если собираем статистику по этой категории
                        busyCats[a.Category] += r.NBusy;//прибавим число занятых ресурсов этой категории в этом блоке
                }
            }
            foreach (string s in busyCats.Keys)
            {//по всем названиям категорий добавим данные
                pts.Add(s, "Categories", sysTime/scaleFactor, busyCats[s]);
            }
        }
        //найдем  сущность с минимальным временем перехода в след. блок
        private Entity GetNextEntity(){
            Entity e;
            nextEventDelay = double.MaxValue;
            int minI = 0;
            for (int i = 0; i < entities.Count; i++)
            {
                if (entities[i].TimeRemained < nextEventDelay)
                {
                    nextEventDelay = entities[i].TimeRemained;
                    minI = i;
                }
            }
            //нашли эту сущность
            e = entities[minI];
            return e;
        }

        public  SimModel()
        {
            pts = new PointsForReport();// создадим список для статистических результатов
            SimulationTimeUnits = TimeUnits.seconds;
            SimulationTime = 1;
            busyCats = new SortedList<string, int>();
            Resources = new List<Resource>();
            Categories = new List<string>();
            Res = new List<Resource>();
            BlockList = new List<Block>();
        }
        private void InitializeStatistics()
        {
            pts.ClearAll();//очистим списки
            busyCats.Clear();
            //Создадим статистические списки для работы...
            foreach (Resource r in Res)//...с блоками ресурсов
            {
                pts.CreateNewList(r.Text,"Resources");
            }
            foreach (string s in Categories)//...с категориями ресурсов
            {
                pts.CreateNewList(s,"Categories");
                busyCats.Add(s, 0);
            }
        }

        /// <summary>
        /// Остановить ИМ
        /// </summary>
        public void Abort()
        {
            shouldSimGo = false;
        }

        /// <summary>
        /// Клонировать текущую модель и все ее содержимое
        /// </summary>
        /// <returns>Клон модели, в котором блоки ресурсов заменены на клоны</returns>
        public SimModel Clone()
        {
            SimModel sim = new SimModel();

            //скопируем блоки
            Block b;
            foreach (Block blc in BlockList)
            {
                b = blc.Clone();
                sim.BlockList.Add(b);
            }
            //скопируем свойства
          
            System.Reflection.PropertyInfo[] properties = sim.GetType().GetProperties();

            int i = 0;

            foreach (System.Reflection.PropertyInfo pi in this.GetType().GetProperties())
            {
                System.ComponentModel.TypeConverter conv = System.ComponentModel.TypeDescriptor.GetConverter(pi.PropertyType);
                if ((pi.CanWrite) && (pi.Name != "BlockList"))//блоки уже не надо копировать
                    properties[i].SetValue(sim, pi.GetValue(this, null), null);
                i++;
            }

            sim.Resources = new List<Resource>();

            //восстановим структуру
            string nm;
            for (i = 0; i < BlockList.Count; i++)
            {
                switch (BlockList[i].GetType().Name)
                {
                    case "Parall":
                        nm = (BlockList[i] as Parall).Next2.Name;
                        (sim.BlockList[i] as Parall).Next2 =
                                sim.BlockList.Find(delegate(Block blc)
                                {
                                    return blc.Name == nm;
                                });
                        goto default;
                    case "Decision":
                        for (int j = 0; j < 3; j++)//для каждого из трех next'ов
                        {
                            b = (BlockList[i] as Decision).Next[j];
                            if (b != null)
                                nm = b.Name;
                            else
                                nm = "";
                            (sim.BlockList[i] as Decision).Next[j] =
                                sim.BlockList.Find(delegate(Block blc)
                            {
                                return blc.Name == nm;
                            });
                        }
                        break;
                    case "Sink":
                        sim.Sink = sim.BlockList[i] as Sink;
                        break;
                    case "Source":
                        sim.Source = sim.BlockList[i] as Source;
                        goto default;
                    case "Action":
                        //разберемся с ресурсами
                        Action a = (sim.BlockList[i] as Action);
                        a.Resources.Clear();
                        foreach (Resource r in (BlockList[i] as Action).Resources)
                        {
                            nm = r.Name;
                            a.Resources.Add(
                                sim.BlockList.Find(delegate(Block blc)
                                {
                                    return blc.Name == nm;
                                }) as Resource);//добавим соответствующий ресурс
                        }
                        goto default;//перейдем к default
                    case "Resource":
                        sim.Resources.Add(sim.BlockList[i] as Resource);
                        break;
                    default:
                        //здесь обработка блоков, у которых только один неподключенный next
                        nm = BlockList[i].Next.Name;
                        sim.BlockList[i].Next =
                                sim.BlockList.Find(delegate(Block blc)
                                {
                                    return blc.Name == nm;
                                });
                        break;
                }
            }

            return sim;
        }
        public override string ToString()
        {
            return "ИМ, Tavr =  " + Convert.ToString(this.Tavr);
        }
    }
}
